﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace WindowsFormsApplication1
{
    public class SocketController
    {
        private Socket socket = null;

        private object lockObj = new Object();
        private List<byte[]> queuedSends = new List<byte[]>();

        private int allowNumParallelSends = 5;
        private int pendingSends = 0;

        private long currentSendID = 0;

        private ClientSocketData csd;

        public SocketController(Socket socket, ClientSocketData csd)
        {
            sendResult = new AsyncCallback(processResult);

            this.socket = socket;
            this.csd = csd;
        }

        private bool closingSocket = false;
        public void Close()
        {
            lock (lockObj)
            {
                if (closingSocket == true)
                    return;

                closingSocket = true;

                closeSocket();
            }
        }

        private void closeSocket()
        {
            try
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Close(5);
                socket.Dispose();
            }
            catch { }
        }

        

        public void SendPolicy()
        {
            lock (lockObj)
            {
                try
                {
                    pendingSends++;

                    byte[] byData = System.Text.Encoding.ASCII.GetBytes("<?xml version=\"1.0\"?><!DOCTYPE cross-domain-policy SYSTEM \"/xml/dtds/cross-domain-policy.dtd\"><cross-domain-policy><site-control permitted-cross-domain-policies=\"all\" /><allow-access-from domain=\"*\" to-ports=\"843-850,2844\" /></cross-domain-policy>" + "\0");
                    socket.BeginSend(byData, 0, byData.Length, SocketFlags.None, sendResult, socket);
                }
                catch { }
            }
        }

        public bool Connected
        {
            get { return socket.Connected; }
        }

        public IAsyncResult BeginReceive(byte[] dataBuffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state)
        {
            lock (lockObj)
            {
                //Do not attempt begin recieve if the socket is closing.
                if (closingSocket)
                    return null;

                try
                {
                    return socket.BeginReceive(dataBuffer, offset, size, socketFlags, callback, state);
                }
                catch
                {
                    queuedSends.Clear();
                    PrimaryServer.instance.closeClientSocket(csd);
                    return null;
                }
            }
        }
        public int EndReceive(IAsyncResult asyncResult)
        {
            try
            {
                return socket.EndReceive(asyncResult);
            }
            catch
            {
                queuedSends.Clear();
                PrimaryServer.instance.closeClientSocket(csd);

                return 0;
            }
        }

        public int Receive(byte[] buffer)
        {
            try
            {
                return socket.Receive(buffer);
            }
            catch
            {
                queuedSends.Clear();
                PrimaryServer.instance.closeClientSocket(csd);

                return 0;
            }
        }

        public int Available
        {
            get { return socket.Available; }
        }

        public void BeginSend(string message)
        {
            BeginSend(message, false);
        }
        public void BeginSend(string message, bool nonCritical)
        {
            lock (lockObj)
            {
                if (closingSocket)
                    return;

                currentSendID++;

                string orderedMessage = "<data id=\"" + currentSendID.ToString() + "\">" + message + "</data>";
                byte[] byData = System.Text.Encoding.ASCII.GetBytes(orderedMessage + "\0");

                if (pendingSends >= allowNumParallelSends)
                {
                    queuedSends.Add(byData);
                }
                else
                {
                    try
                    {
                        socket.BeginSend(byData, 0, byData.Length, SocketFlags.None, sendResult, socket);
                        pendingSends++;
                    }
                    catch (Exception ex)
                    {
                        queuedSends.Clear();
                        PrimaryServer.instance.closeClientSocket(csd);

                        return;
                    }
                }
            }
        }

        private AsyncCallback sendResult;
        private void processResult(IAsyncResult result)
        {
            SocketError se;

            try
            {
                socket.EndSend(result, out se);
            }
            catch
            {
                queuedSends.Clear();
                PrimaryServer.instance.closeClientSocket(csd);

                return;
            }

            lock (lockObj)
            {
                pendingSends--;

                if (queuedSends.Count > 0 && closingSocket == false)
                {
                    byte[] queuedSend = queuedSends[0];
                    queuedSends.RemoveAt(0);

                    try
                    {
                        socket.BeginSend(queuedSend, 0, queuedSend.Length, SocketFlags.None, sendResult, socket);
                        pendingSends++;
                    }
                    catch
                    {
                        queuedSends.Clear();
                        PrimaryServer.instance.closeClientSocket(csd);

                        return;
                    }
                }
            }
        }
    }
}
